// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Do not remove this, it is needed to retain calls to these conditional methods in release builds
#define DEBUG

using System.Diagnostics.CodeAnalysis;

namespace System.Diagnostics
{
    /// <summary>
    /// Provides default implementation for Write and Fail methods in Debug class.
    /// </summary>
    public partial class DebugProvider
    {
        [DoesNotReturn]
        public virtual void Fail(string? message, string? detailMessage)
        {
            string stackTrace;
            try
            {
                stackTrace = new StackTrace(0, true).ToString(StackTrace.TraceFormat.Normal);
            }
            catch
            {
                stackTrace = "";
            }
            WriteAssert(stackTrace, message, detailMessage);
            FailCore(stackTrace, message, detailMessage, "Assertion failed.");
#pragma warning disable 8763 // "A method marked [DoesNotReturn] should not return."
        }
#pragma warning restore 8763

        internal void WriteAssert(string stackTrace, string? message, string? detailMessage)
        {
            WriteLine(SR.DebugAssertBanner + Environment.NewLineConst
                   + SR.DebugAssertShortMessage + Environment.NewLineConst
                   + message + Environment.NewLineConst
                   + SR.DebugAssertLongMessage + Environment.NewLineConst
                   + detailMessage + Environment.NewLineConst
                   + stackTrace);
        }

        public virtual void Write(string? message)
        {
            lock (s_lock)
            {
                if (message == null)
                {
                    WriteCore(string.Empty);
                    return;
                }
                if (_needIndent)
                {
                    message = GetIndentString() + message;
                    _needIndent = false;
                }
                WriteCore(message);
                if (message.EndsWith(Environment.NewLineConst, StringComparison.Ordinal))
                {
                    _needIndent = true;
                }
            }
        }

        public virtual void WriteLine(string? message)
        {
            Write(message + Environment.NewLineConst);
        }

        public virtual void OnIndentLevelChanged(int indentLevel) { }

        public virtual void OnIndentSizeChanged(int indentSize) { }

        private static readonly object s_lock = new object();

        private sealed class DebugAssertException : Exception
        {
            internal DebugAssertException(string? message, string? detailMessage, string? stackTrace) :
                base(Terminate(message) + Terminate(detailMessage) + stackTrace)
            {
            }

            private static string? Terminate(string? s)
            {
                if (s == null)
                    return s;

                s = s.Trim();
                if (s.Length > 0)
                    s += Environment.NewLineConst;

                return s;
            }
        }

        private bool _needIndent = true;

        private string? _indentString;

        private string GetIndentString()
        {
            int indentCount = Debug.IndentSize * Debug.IndentLevel;
            if (_indentString?.Length == indentCount)
            {
                return _indentString;
            }
            return _indentString = new string(' ', indentCount);
        }

        // internal and not readonly so that the tests can swap this out.
        internal static Action<string, string?, string?, string>? s_FailCore;
        internal static Action<string>? s_WriteCore;
    }
}
